use std::fmt;
use std::ops;

/// The value we consider below to be "almost zero".
const VEC3F_ZERO_THRESH: f32 = 0.0001;

/// This is low enough that we'd probably not notice the uncertainty.
const AAVEC3F_COLLAPSE_THRESH: f32 = 0.0002;

/// Integer (i32) 3D vector.
#[derive(Copy, Clone, Hash, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct Vec3i(i32, i32, i32);

impl Vec3i {
    pub fn new(x: i32, y: i32, z: i32) -> Self {
        Vec3i(x, y, z)
    }

    pub fn new_y(y: i32) -> Self {
        Self::new(0, y, 0)
    }

    pub fn new_xz(x: i32, z: i32) -> Self {
        Self::new(x, 0, z)
    }

    pub fn zero() -> Self {
        Self::new(0, 0, 0)
    }

    pub fn x(&self) -> i32 {
        self.0
    }

    pub fn y(&self) -> i32 {
        self.1
    }

    pub fn z(&self) -> i32 {
        self.2
    }

    pub fn abs(&self) -> Vec3i {
        Self::new(self.0.abs(), self.1.abs(), self.2.abs())
    }

    /// Returns the value of the component of the vector on the axis.
    pub fn component(&self, axis: Axis) -> i32 {
        match axis {
            Axis::X => self.x(),
            Axis::Y => self.y(),
            Axis::Z => self.z(),
        }
    }
}

impl From<(i32, i32, i32)> for Vec3i {
    fn from((x, y, z): (i32, i32, i32)) -> Self {
        Self::new(x, y, z)
    }
}

impl Into<(i32, i32, i32)> for Vec3i {
    fn into(self) -> (i32, i32, i32) {
        (self.0, self.1, self.2)
    }
}

impl Into<(i32, i32, i32)> for &Vec3i {
    fn into(self) -> (i32, i32, i32) {
        (self.0, self.1, self.2)
    }
}

impl ops::Add for Vec3i {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        Self(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2)
    }
}

impl ops::Sub for Vec3i {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self::Output {
        Self(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2)
    }
}

impl ops::Mul<i32> for Vec3i {
    type Output = Self;

    fn mul(self, rhs: i32) -> Self::Output {
        Self(self.0 * rhs, self.1 * rhs, self.2 * rhs)
    }
}

impl ops::Neg for Vec3i {
    type Output = Self;

    fn neg(self) -> Self::Output {
        Self(-self.0, -self.1, -self.2)
    }
}

impl fmt::Debug for Vec3i {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let Vec3i(x, y, z) = *self;
        f.write_fmt(format_args!("<{x},{y},{z}>"))
    }
}

impl fmt::Display for Vec3i {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        <Self as fmt::Debug>::fmt(self, f)
    }
}

/// Integer (f32) 3D vector.
#[derive(Copy, Clone, Default, PartialEq, PartialOrd)]
pub struct Vec3f(f32, f32, f32);

impl Vec3f {
    pub fn new(x: f32, y: f32, z: f32) -> Self {
        Vec3f(x, y, z)
    }

    pub fn new_y(y: f32) -> Self {
        Self::new(0.0, y, 0.0)
    }

    pub fn new_xz(x: f32, z: f32) -> Self {
        Self::new(x, 0.0, z)
    }

    pub fn zero() -> Self {
        Self::new(0.0, 0.0, 0.0)
    }

    pub fn x(&self) -> f32 {
        self.0
    }

    pub fn y(&self) -> f32 {
        self.1
    }

    pub fn z(&self) -> f32 {
        self.2
    }

    /// Returns the length of the vector.
    pub fn magnitude(&self) -> f32 {
        let Self(x, y, z) = self;
        f32::sqrt(x * x + y * y + z * z)
    }

    /// Returns if the vector is exactly 0 in all components.
    pub fn is_exactly_zero(&self) -> bool {
        self.x() == 0.0 && self.y() == 0.0 && self.z() == 0.0
    }

    /// Returns if the vector is below a reasonable near-zero threshold in all components.
    pub fn is_roughly_zero(&self) -> bool {
        (self.x().abs() < VEC3F_ZERO_THRESH)
            && (self.y().abs() < VEC3F_ZERO_THRESH)
            && (self.z().abs() < VEC3F_ZERO_THRESH)
    }

    /// Returns a vector pointing in the same direction but with a length of 1.
    pub fn normalized(&self) -> Vec3f {
        let mag = self.magnitude();
        Self::new(self.x() / mag, self.y() / mag, self.z() / mag)
    }

    pub fn abs(&self) -> Vec3f {
        Self::new(self.0.abs(), self.1.abs(), self.2.abs())
    }

    /// Returns the value of the component of the vector on the axis.
    pub fn component(&self, axis: Axis) -> f32 {
        match axis {
            Axis::X => self.x(),
            Axis::Y => self.y(),
            Axis::Z => self.z(),
        }
    }

    /// Computes the direct sum of the vector components, not the length unless
    /// only 1 is nonzero.
    pub fn component_sum(&self) -> f32 {
        self.x() + self.y() + self.z()
    }

    /// Scales the vector component-wise by another vector.
    pub fn scale(&self, by: Vec3f) -> Vec3f {
        Vec3f::new(self.x() * by.x(), self.y() * by.y(), self.z() * by.z())
    }

    /// Returns if any component is a NaN.
    pub fn is_any_nan(&self) -> bool {
        self.x().is_nan() || self.y().is_nan() || self.z().is_nan()
    }

    /// Returns self if physically safe, otherwise returns zero.
    pub fn to_phys_safe(&self) -> Vec3f {
        if self.is_any_nan() {
            Self::zero()
        } else {
            *self
        }
    }

    /// Linearly interpolates to a target vec3f.
    pub fn lerp_to(&self, dest: Vec3f, dest_prop: f32) -> Self {
        let self_prop = 1.0 - dest_prop;
        (*self * self_prop) + (dest * dest_prop)
    }
}

impl From<Vec3i> for Vec3f {
    fn from(value: Vec3i) -> Self {
        Self::new(value.x() as f32, value.y() as f32, value.z() as f32)
    }
}

impl From<(f32, f32, f32)> for Vec3f {
    fn from((x, y, z): (f32, f32, f32)) -> Self {
        Self::new(x, y, z)
    }
}

impl Into<(f32, f32, f32)> for Vec3f {
    fn into(self) -> (f32, f32, f32) {
        (self.0, self.1, self.2)
    }
}

impl Into<(f32, f32, f32)> for &Vec3f {
    fn into(self) -> (f32, f32, f32) {
        (self.0, self.1, self.2)
    }
}

impl ops::Add for Vec3f {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        Self(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2)
    }
}

impl ops::AddAssign for Vec3f {
    fn add_assign(&mut self, rhs: Self) {
        self.0 += rhs.0;
        self.1 += rhs.1;
        self.2 += rhs.2;
    }
}

impl ops::Sub for Vec3f {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self::Output {
        Self(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2)
    }
}

impl ops::Mul<f32> for Vec3f {
    type Output = Self;

    fn mul(self, rhs: f32) -> Self::Output {
        Self(self.0 * rhs, self.1 * rhs, self.2 * rhs)
    }
}

impl ops::MulAssign<f32> for Vec3f {
    fn mul_assign(&mut self, rhs: f32) {
        self.0 *= rhs;
        self.1 *= rhs;
        self.2 *= rhs;
    }
}

impl ops::Neg for Vec3f {
    type Output = Self;

    fn neg(self) -> Self::Output {
        Self(-self.0, -self.1, -self.2)
    }
}

impl fmt::Debug for Vec3f {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let Vec3f(x, y, z) = *self;
        f.write_fmt(format_args!("<{x},{y},{z}>"))
    }
}

impl fmt::Display for Vec3f {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        <Self as fmt::Debug>::fmt(self, f)
    }
}

/// Axis-aligned direction, ordered according to vector ordering.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum Direction {
    /// +X
    East,

    /// -X
    West,

    /// +Y
    Up,

    /// -Y
    Down,

    /// +Z
    South,

    /// -Z
    North,
}

impl Direction {
    /// Converts a direction to a unit vector pointing in that direction.
    pub fn to_unit_vec(self) -> Vec3i {
        let tup = match self {
            Self::East => (1, 0, 0),
            Self::West => (-1, 0, 0),
            Self::Up => (0, 1, 0),
            Self::Down => (0, -1, 0),
            Self::South => (0, 0, 1),
            Self::North => (0, 0, -1),
        };
        Vec3i::from(tup)
    }

    /// Tries to parse a direction from a unit `Vec3i`.
    pub fn try_from_unit_vec(v: Vec3i) -> Option<Self> {
        Some(match v.into() {
            (1, 0, 0) => Self::East,
            (-1, 0, 0) => Self::West,
            (0, 1, 0) => Self::Up,
            (0, -1, 0) => Self::Down,
            (0, 0, 1) => Self::South,
            (0, 0, -1) => Self::North,
            _ => return None,
        })
    }

    /// Returns the direction in the opposite direction.
    pub fn opposite(self) -> Self {
        match self {
            Self::East => Self::West,
            Self::West => Self::East,
            Self::Up => Self::Down,
            Self::Down => Self::Up,
            Self::South => Self::North,
            Self::North => Self::South,
        }
    }

    /// Returns the axis that this direction is along.
    pub fn axis(self) -> Axis {
        match self {
            Self::East | Self::West => Axis::X,
            Self::Up | Self::Down => Axis::Y,
            Self::South | Self::North => Axis::Z,
        }
    }

    /// Returns the "sign" of this direction along its axis.
    pub fn sign_i32(self) -> i32 {
        match self {
            Self::East | Self::Up | Self::South => 1,
            Self::West | Self::Down | Self::North => -1,
        }
    }

    /// Returns the "sign" of this direction along its axis.
    pub fn sign_f32(self) -> f32 {
        match self {
            Self::East | Self::Up | Self::South => 1.0,
            Self::West | Self::Down | Self::North => -1.0,
        }
    }

    /// All the directions in their canonical order.
    #[inline(always)]
    pub fn array() -> [Direction; 6] {
        [
            Self::East,
            Self::West,
            Self::Up,
            Self::Down,
            Self::South,
            Self::North,
        ]
    }

    /// Converts the direction to an index, corresponding to the result of
    /// `array()`.
    #[inline(always)]
    pub fn to_index(&self) -> usize {
        match self {
            Self::East => 0,
            Self::West => 1,
            Self::Up => 2,
            Self::Down => 3,
            Self::South => 4,
            Self::North => 5,
        }
    }

    /// Converts from an index to a direction, if possible.
    #[inline(always)]
    pub fn from_index(idx: usize) -> Option<Self> {
        Some(match idx {
            0 => Self::East,
            1 => Self::West,
            2 => Self::Up,
            3 => Self::Down,
            4 => Self::South,
            5 => Self::North,
            _ => return None,
        })
    }

    /// Creates an axis-aligned vec3f of some value along this axis.
    ///
    /// Passing a negative value results in a vector going in the *other* direction.
    pub fn to_aa_vec(&self, l: f32) -> AAVec3f {
        AAVec3f::new(self.axis(), l * self.sign_f32())
    }
}

impl ops::Neg for Direction {
    type Output = Self;

    fn neg(self) -> Self::Output {
        self.opposite()
    }
}

/// An axis in the world.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum Axis {
    X,
    Y,
    Z,
}

impl Axis {
    pub fn array() -> [Axis; 3] {
        [Self::X, Self::Y, Self::Z]
    }

    /// Converts the axis to an index, corresponding to the result of `array()`.
    #[inline(always)]
    pub fn to_index(&self) -> usize {
        match self {
            Self::X => 0,
            Self::Y => 1,
            Self::Z => 2,
        }
    }

    /// Converts from an index to an axis, if possible.
    #[inline(always)]
    pub fn from_index(idx: usize) -> Option<Self> {
        Some(match idx {
            0 => Self::X,
            1 => Self::Y,
            2 => Self::Z,
            _ => return None,
        })
    }

    /// Returns the positive and negative directions along this axis.
    pub fn dirs(&self) -> (Direction, Direction) {
        match self {
            Self::X => (Direction::East, Direction::West),
            Self::Y => (Direction::Up, Direction::Down),
            Self::Z => (Direction::South, Direction::North),
        }
    }

    /// Returns the positive direction along this axis.
    pub fn positive_dir(&self) -> Direction {
        self.dirs().0
    }

    /// Returns the negative direction along this axis.
    pub fn negative_dir(&self) -> Direction {
        self.dirs().1
    }

    /// Returns the two axes perpendicular to this axis.
    pub fn flat_axes(&self) -> (Axis, Axis) {
        match self {
            Self::X => (Self::Y, Self::Z),
            Self::Y => (Self::Z, Self::X),
            Self::Z => (Self::X, Self::Y),
        }
    }

    /// Creates an axis-aligned vec3f of some value along this axis.
    pub fn to_aa_vec(&self, l: f32) -> AAVec3f {
        AAVec3f::new(*self, l)
    }
}

/// An axis-aligned floating-point vector.
#[derive(Copy, Clone, Debug)]
pub struct AAVec3f(Axis, f32);

impl AAVec3f {
    pub fn new(axis: Axis, len: f32) -> Self {
        Self(axis, len)
    }

    /// Takes an existing vector and collapses it into an AA representation if
    /// it's close enough to being on the axis.  If all components are below the
    /// threshold, returns `None`.
    pub fn try_from_vec3f_thresh(v: Vec3f, thresh: f32) -> Option<Self> {
        let xa = v.x().abs();
        let ya = v.y().abs();
        let za = v.z().abs();
        let xt = xa >= thresh;
        let yt = ya >= thresh;
        let zt = za >= thresh;
        Some(match (xt, yt, zt) {
            (true, false, false) => Self::new(Axis::X, v.x()),
            (false, true, false) => Self::new(Axis::Y, v.y()),
            (false, false, true) => Self::new(Axis::Z, v.z()),
            _ => return None,
        })
    }

    /// Like `try_from_vec3f` but with a fixed comfortably low threshold.
    pub fn try_from_vec3f(v: Vec3f) -> Option<Self> {
        Self::try_from_vec3f_thresh(v, AAVEC3F_COLLAPSE_THRESH)
    }

    /// Returns the axis this vector is along.
    pub fn axis(&self) -> Axis {
        self.0
    }

    /// Returns the value of this vector's component.
    pub fn value(&self) -> f32 {
        self.1
    }

    /// Returns the X component of this vector.
    pub fn x(&self) -> f32 {
        match self.axis() {
            Axis::X => self.value(),
            _ => 0.0,
        }
    }

    /// Returns the Y component of this vector.
    pub fn y(&self) -> f32 {
        match self.axis() {
            Axis::Y => self.value(),
            _ => 0.0,
        }
    }

    /// Returns the Z component of this vecto.r
    pub fn z(&self) -> f32 {
        match self.axis() {
            Axis::Z => self.value(),
            _ => 0.0,
        }
    }

    /// Returns the length of this vector.
    pub fn len(&self) -> f32 {
        self.value().abs()
    }

    /// Returns the direction facing along this vector.
    pub fn direction(&self) -> Direction {
        if self.value() >= 0.0 {
            self.axis().positive_dir()
        } else {
            self.axis().negative_dir()
        }
    }

    /// Returns the vector normalized to be of length 1.
    pub fn normalized(&self) -> AAVec3f {
        if self.len() >= 0.0 {
            Self(self.axis(), 1.0)
        } else {
            Self(self.axis(), -1.0)
        }
    }

    /// Returns the value of the component of the vector on the axis.
    pub fn component(&self, axis: Axis) -> f32 {
        if axis == self.axis() {
            self.value()
        } else {
            0.0
        }
    }
    /// Scales the vector component-wise by another vector.
    pub fn scale(&self, by: Vec3f) -> AAVec3f {
        let nl = by.component(self.axis()) * self.len();
        Self::new(self.axis(), nl)
    }

    /// Returns if any component is a NaN.
    pub fn is_any_nan(&self) -> bool {
        self.len().is_nan()
    }

    /// Returns if this vector is colinear with another `AAVec3f`.
    pub fn colinear_with(&self, v: &AAVec3f) -> bool {
        self.axis() == v.axis()
    }
}

impl Into<Vec3f> for AAVec3f {
    fn into(self) -> Vec3f {
        Vec3f::from(self.axis().positive_dir().to_unit_vec()) * self.len()
    }
}

impl ops::Neg for AAVec3f {
    type Output = Self;

    fn neg(self) -> Self::Output {
        Self::new(self.axis(), -self.value())
    }
}
